<?php
require_once 'Service/Model.php';

class Service_Model_CacheableModel extends Service_Model
{
    const CACHE_TAG = 'topics';

    /**
     * @var array
     */
    protected $_relatedCacheTags = array(self::CACHE_TAG);
    
    /**
     * @var Zend_Cache_Core
     */
    protected static $_defaultCache = null;

    /**
     * @var integer
     */
    protected $_cacheLifetime = false;

    /**
     *
     * @var Zend_Cache_Core
     */
    protected $_cache = null;
    
    /**
     * Sets a default cache for model.
     *
     * @param Zend_Cache_Core $cache
     */
    public static function setDefaultCache($cache)
    {
        if (!$cache instanceof Zend_Cache_Core && null !== $cache) {
            require_once 'Service/Entity/Exception.php';
            throw new Zend_Db_Table_Exception('Argument must be of type Zend_Cache_Core');
        }
        self::$_defaultCache = $cache;
    }

    /**
     * Gets a default cache for model.
     *
     * @return Zend_Cache_Core
     */
    public static function getDefaultCache()
    {
        return self::$_defaultCache;
    }

    /**
     * Constructor.
     *
     * @param Service_DataSource $ds
     * @param Zend_Cache_Core $cache
     */
    public function  __construct($ds = null, $cache = null)
    {
        parent::__construct($ds);
        if ($cache) {
            $this->setCache($cache);
        } else {
            $this->setCache(self::getDefaultCache());
        }
    }
    
    /**
     * Sets a cache for model.
     *
     * @param Zend_Cache_Core $cache
     */
    public function setCache($cache)
    {
        if (!$cache instanceof Zend_Cache_Core && null !== $cache) {
            require_once 'Service/Entity/Exception.php';
            throw new Service_Entity_Exception('Argument must be instance of Zend_Cache_Core.');
        }
       $this->_cache = $cache;
    }

    /**
     * Gets a cache for model.
     *
     * @return Zend_Cache_Core
     */
    public function getCache()
    {
        return $this->_cache;
    }

    /**
     * Sets related cache tags.
     *
     * @param array $cacheTags
     */
    public function setRelatedCacheTags($cacheTags)
    {
        $this->_relatedCacheTags = $cacheTags;
    }

    /**
     * Gets related cache tags.
     * @return array
     */
    public function getRelatedCacheTags()
    {
        return $this->_relatedCacheTags;
    }

    /**
     * Sets cache lifetime.
     *
     * @param integer cacheLifetime
     */
    public function setCacheLifetime($cacheLifetime)
    {
        $this->_cacheLifetime = $cacheLifetime;
    }

    /**
     * Gets cache lifetime.
     *
     * @return integer
     */
    public function getCacheLifetime()
    {
        return $this->_cacheLifetime;
    }
    
    /**
     * Gets a single item.
     *
     * @param mixed $id
     * @param array $attrList
     * @return Service_Entity_CommonEntity
     */
    public function getItem($id, $attrList = null)
    {
        $cache = $this->getCache();
        $cacheId = $this->_getItemCacheId($id, $attrList);

        $item = $cache->load($cacheId);

        if (!$item) {
            // get from db and store to cache
            $item = parent::getItem($id, $attrList);
            $tags = $this->getRelatedCacheTags();
            $tags[] = $this->_getItemCacheTag($id);
            $cache->save($item, $cacheId, $tags, $this->getCacheLifetime());
        }

        return $item;
    }

    public function putItem($id, $item)
    {
        $res = parent::putItem($id, $item);

        if ($res) {
            // expire list cache if a new item was added
            $this->_expireListCache();
            
            if ($id) {
                // expire item cache if it was updated
                $this->_expireItemCache($id);
            }
        }

        return $res;
    }

    public function deleteItem($id)
    {
        $res = parent::deleteItem($id);

        if ($res) {
            // expire list cache if a new item was added
            $this->_expireListCache();

            if ($id) {
                // expire item cache if it was updated
                $this->_expireItemCache($id);
            }
        }

        return $res;
    }

    public function countItems()
    {
        $cache = $this->getCache();
        $cacheId = $this->_getCountCacheId();

        $count = $cache->load($cacheId);

        if (!$count) {
            $count = parent::countItems();
            $tags = $this->getRelatedCacheTags();
            $tags[] = $this->getListCacheTag();
            $cache->save($count, $cacheId, $tags, $this->getCacheLifetime());
        }

        return $count;
    }

    public function listItems($attrList = null, $limit = 100, $offset = 0, $sort = null)
    {
        $cache = $this->getCache();
        $cacheId = $this->_getListCacheId($attrList, $limit, $offset, $sort);

        $list = $cache->load($cacheId);

        if (!$list) {
            $list = parent::listItems($attrList, $limit, $offset, $sort);
            $tags = $this->getRelatedCacheTags();
            $tags[] = $this->getListCacheTag();
            $cache->save($list, $cacheId, $tags, $this->getCacheLifetime());
        }

        return $list;
    }

     public function updateItems($fields)
    {
        $res = parent::updateItems($fields);

        if ($res) {
            $this->getCache()->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG,
                $this->getRelatedCacheTags());
        }

        return $res;
    }

    public function removeItems()
    {
        $res = parent::removeItems();

        if ($res) {
            $this->getCache()->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG,
                $this->getRelatedCacheTags());
        }

        return $res;
    }

    public function getListCacheTag()
    {
        return 'list_' . get_class($this);
    }

    protected function _getItemCacheTag($id)
    {
        $key = array($this->_identifier => $id);
        return get_class($this) . '_'. md5(serialize($key));
    }

    protected function _getItemCacheId($id, $attrs)
    {
        $key = array($this->_identifier => $id);
        return 'item_' . $this->_getItemCacheTag($id) . '_'
            . md5(serialize($attrs));
    }

    protected function _getListCacheId($attrList, $limit, $offset, $sort)
    {
        $options = array($attrList, $limit, $offset, $sort, $this->_filters);
        return $this->getListCacheTag() . '_'. md5(serialize($options));
    }

    protected function _getCountCacheId()
    {
        return 'count_' . get_class($this) . '_'. md5(serialize($this->_filters));
    }
    
    protected function _expireItemCache($id)
    {
        $this->getCache()->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG,
                array($this->_getItemCacheTag($id)));
    }

    protected function _expireListCache()
    {
        $this->getCache()->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG,
                array($this->getListCacheTag()));
    }

    protected function _expireAllCache()
    {
        $this->getCache()->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG,
                $this->getRelatedCacheTags());
    }
}
